import { isObjectValueEqual } from '@/utils/obj/equal';
import {
  useContext,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from 'react';

import DndContext from '../../context/DndContext';
import DragSourceImpl from '../classes/DragSourceImpl';
import DragSourceMonitorImpl from '../classes/DragSourceMonitorImple';
import type {
  DragSource,
  DragSourceHookSpec,
  DragSourceMonitor,
  FactoryOrInstance,
} from '../dragInterface';

export function useDrag<
  DragItem = unknown,
  DropResult = unknown,
  CollectedProps = Record<string, any>,
>(
  specArg: FactoryOrInstance<
    DragSourceHookSpec<DragItem, DropResult, CollectedProps>
  >, //DragSourceHookSpec | () => DragSourceHookSpec
  deps?: unknown[],
): [CollectedProps] {
  const [sourceId, setSourceId] = useState<string>();
  const lastConnectdragNodeRef = useRef<HTMLElement>();
  const dragNodeUnBindDragStartFnRef = useRef<Function>();
  const [selectedProps, setSelectedProps] = useState<CollectedProps>(
    {} as CollectedProps,
  );

  const { dragDropManager } = useContext(DndContext);
  const monitorRef = useRef<DragSourceMonitor<DragItem, DropResult>>();
  const dragSourceRef = useRef<DragSource>();

  // useEffect(() => {},[]); 监听实例对象， 其实和对象一样，实例对象的属性发生变化也会触发监听

  // 1.useDrag({type,dragNode,item,collect},[])参数整合
  const memoDeps = [...(deps || [])];
  if (!deps && typeof specArg !== 'function') {
    memoDeps.push(specArg);
  }

  const spec = useMemo(() => {
    if (typeof specArg === 'function') return specArg();
    return specArg;
  }, memoDeps);

  useLayoutEffect(() => {
    // // console.log('--useDrag--useLayoutEffect执行多次？？？，因为是新增啊，列表新增一个item啊---')
    const { type } = spec ?? {};
    if (!type) {
      throw new Error('useDrag:请设置type');
    }
    if (!dragDropManager) return;
    const monitor = new DragSourceMonitorImpl(dragDropManager); //2.当前 DragSource对应的 监听器实例
    const dragSource = new DragSourceImpl<DragItem, DropResult, CollectedProps>(
      spec,
      monitor,
    ); // 3.生成拖动源实例
    monitorRef.current = monitor;
    dragSourceRef.current = dragSource;

    const registry = dragDropManager.getRegistry();
    const sourceId = registry.addSource(spec.type, dragSource); //4.注册拖动源实例到注册表中
    setSourceId(sourceId);
    monitor.receiveHandlerId(sourceId);
    // // console.log('--types--dragSources--:',registry.types,registry.dragSources);

    return () => {
      if (typeof dragNodeUnBindDragStartFnRef.current === 'function') {
        dragNodeUnBindDragStartFnRef.current(); //dargNode取消绑定dragStart/End事件
        lastConnectdragNodeRef.current = undefined;
      }
      if (sourceId) {
        registry.removeSource(sourceId);
      }
    };
  }, []);

  useEffect(() => {
    if (!dragDropManager || !monitorRef.current || !dragSourceRef.current)
      return;
    const newSpec = { ...spec };
    dragSourceRef.current.setSpec(newSpec);
    const { type, dragNode, collect } = newSpec ?? {};
    if (type && dragNode && sourceId) {
      if (
        !lastConnectdragNodeRef.current ||
        lastConnectdragNodeRef.current !== dragNode
      ) {
        if (typeof dragNodeUnBindDragStartFnRef.current === 'function') {
          dragNodeUnBindDragStartFnRef.current();
        }
        lastConnectdragNodeRef.current = dragNode;
        // // console.log('--useDrag--useEffect到底是不是只执行一次---')
        dragNodeUnBindDragStartFnRef.current =
          dragDropManager.bindDragEventsForDragNode(sourceId, dragNode); // 5.dargNode绑定dragStart/End事件
        if (typeof collect === 'function') {
          monitorRef.current.subscribeStateChange(() => {
            //6.当前正在拖拽的数据（redux）变化时更新collect函数的返回值
            const newValue = collect(monitorRef.current!);
            if (!isObjectValueEqual(newValue, selectedProps)) {
              setSelectedProps({ ...newValue });
            }
          });
        }
      }
    }
  }, [spec, sourceId]);

  return [selectedProps];
}
